/*
 * Copyright 2007 Seok-Ho, Yang | Cyril Joui (FR)
 * Original code from Codehaus Foundation (ASF)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package kr.javanese.devtools.m2wtp.common;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.InvalidArtifactRTException;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.embedder.MavenEmbedder;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.project.InvalidProjectModelException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.validation.ModelValidationResult;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.maven.ide.eclipse.Maven2Plugin;
import org.maven.ide.eclipse.Messages;
import org.maven.ide.eclipse.embedder.MavenEmbedderManager;
import org.maven.ide.eclipse.embedder.MavenModelManager;

/**
 * Utility class forgetting information about pom.xml file (of a project).
 * 
 * @author Cyril Joui (FR)
 * @author Seok-Ho, Yang (KR)
 */
public final class BuildPathManager {

	/**
	 * Maven IDE plugin dependency.
	 */
	private static MavenEmbedderManager embedderManager;

	/**
	 * Maven IDE plugin dependency.
	 */
	private static MavenModelManager mavenModelManager;

	/**
	 * Constructor not used.
	 * Cause of utility class.
	 */
	private BuildPathManager() {
	}

	/**
	 * Static initializer
	 */
	static {
		embedderManager = Maven2Plugin.getDefault().getMavenEmbedderManager();
		mavenModelManager = Maven2Plugin.getDefault().getMavenModelManager();
	}

	/**
	 * Parse pom file and return a MavenClassPathList object.
	 * @param pomFile
	 * @param monitor
	 * @return a MavenClassPathList object built on given pom.xml file.
	 * @throws CoreException
	 */
	public static MavenClassPathList getClasspathList(IFile pomFile,
			IProgressMonitor monitor) throws CoreException {
		// use null monitor if no monitor
		if (monitor == null) {
			monitor = new NullProgressMonitor();
		}

		try {
			mavenModelManager.updateMavenModel(pomFile, true, monitor);
		} catch (CoreException ex) {
//			addMarker(pomFile, ex.getMessage(), 1, IMarker.SEVERITY_ERROR);
		}

		MavenClassPathList classPathList = new MavenClassPathList();
		resolveClasspathEntries(classPathList, new HashMap<String, Artifact>(), pomFile, pomFile, true, monitor);
		return classPathList;
	}

	/**
	 * Load dependencies of given pom.xml file.
	 * @param classPathList
	 * @param moduleArtifacts
	 * @param rootPomFile
	 * @param pomFile
	 * @param recursive
	 * @param monitor
	 */
	private static void resolveClasspathEntries(MavenClassPathList classPathList, Map<String, Artifact> moduleArtifacts, IFile rootPomFile,
			IFile pomFile, boolean recursive, IProgressMonitor monitor) {
		if (monitor.isCanceled()) {
			throw new OperationCanceledException();
		}

		if (pomFile == null
				|| !pomFile.isAccessible()) {
			return;
		}

		monitor.subTask("Reading " + pomFile.getFullPath());

		IProject currentProject = pomFile.getProject();
		try {
			boolean offline = false;
			boolean downloadSources = !offline & false;
			boolean downloadJavadoc = !offline & false;
			boolean debug = true;

			MavenExecutionResult result = mavenModelManager.readMavenProject(
					pomFile, monitor, offline, debug);
			MavenProject mavenProject = getMavenProject(pomFile, result);
			if (mavenProject == null) {
				return;
			}

			MavenEmbedder embedder = embedderManager.getProjectEmbedder();

			// iterate on artifacts
			Set artifacts = mavenProject.getArtifacts();
			for (Iterator it = artifacts.iterator(); it.hasNext();) {
				if (monitor.isCanceled()) {
					throw new OperationCanceledException();
				}

				Artifact currentArtifact = (Artifact) it.next();
				String artifactLocation = currentArtifact.getFile().getAbsolutePath();

				monitor.subTask("Processing " + currentArtifact.getId());

				// String artifactKey = a.getGroupId() + ":" + a.getArtifactId()
				// + ":" + a.getVersion() + ":" + a.getType();

				if (moduleArtifacts.containsKey(artifactLocation)) {
					continue;
				}

				ArtifactHandler artifactHandler = embedder
						.getArtifactHandler(currentArtifact);
				if (!artifactHandler.isAddedToClasspath()
						|| !("jar".equals(artifactHandler.getExtension())
								|| "zip".equals(artifactHandler.getExtension()))) {
					continue;
				}

				moduleArtifacts.put(artifactLocation, currentArtifact);
				mavenModelManager.addProjectArtifact(pomFile, currentArtifact);
				// this is needed to projects with have modules (either inner or
				// external)
				mavenModelManager.addProjectArtifact(rootPomFile, currentArtifact);

				// new classpath entry to add
				IClasspathEntry classPathEntryToAdd;
				
				IFile artifactPomFile = mavenModelManager.getArtifactFile(currentArtifact);
				if (artifactPomFile != null) {
					IProject artifactProject = artifactPomFile.getProject();
					if (artifactProject.getFullPath().equals(
							currentProject.getFullPath())) {
						// This is another artifact in our current project so we
						// should not
						// add our own project to ourself
						continue;
					}

					classPathEntryToAdd = JavaCore.newProjectEntry(artifactProject
							.getFullPath(), false);
				} else {
					Path srcPath = materializeArtifactPath(embedder, mavenProject,
							currentArtifact, "java-source", "sources", downloadSources, monitor);
	
					IClasspathAttribute[] attributes = new IClasspathAttribute[0];
					if (srcPath == null) { // no need to search for javadoc if we
											// have source code
						Path javadocPath = materializeArtifactPath(embedder,
								mavenProject, currentArtifact, "javadoc", "javadoc",
								downloadJavadoc, monitor);
						/*
						String javaDocUrl = null;
						javaDocUrl = getJavaDocUrl(artifactLocation, monitor);
						if (javaDocUrl != null) {
							attributes = new IClasspathAttribute[] { JavaCore
									.newClasspathAttribute(
											IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
											javaDocUrl) };
						}
						*/
					}
	
					classPathEntryToAdd = JavaCore.newLibraryEntry(new Path(
							artifactLocation), srcPath, null, new IAccessRule[0],
							attributes, false ); // not exported
				}

				// add new classpath entry
				if (Artifact.SCOPE_PROVIDED.equals(currentArtifact.getScope())) {
					classPathList.getProvidedDependencies().add(classPathEntryToAdd);
				} else if (Artifact.SCOPE_TEST.equals(currentArtifact.getScope())) {
					classPathList.getTestDependencies().add(classPathEntryToAdd);
				} else if (Artifact.SCOPE_COMPILE.equals(currentArtifact.getScope())) {
					classPathList.getCompileDependencies().add(classPathEntryToAdd);
				} else if (Artifact.SCOPE_RUNTIME.equals(currentArtifact.getScope())) {
					classPathList.getRuntimeDependencies().add(classPathEntryToAdd);
				} else {
					classPathList.getCompileDependencies().add(classPathEntryToAdd);
				}
			}

//			 if (recursive) {
//				IContainer parent = pomFile.getParent();
//
//				List modules = mavenProject.getModules();
//				for (Iterator it = modules.iterator(); it.hasNext()
//						&& !monitor.isCanceled();) {
//					if (monitor.isCanceled()) {
//						throw new OperationCanceledException();
//					}
//
//					String module = (String) it.next();
//					IResource memberPom = parent.findMember(module + "/"
//							+ Maven2Plugin.POM_FILE_NAME);
//					if (memberPom != null
//							&& memberPom.getType() == IResource.FILE) {
//						resolveClasspathEntries(libraryEntries,
//								moduleArtifacts, rootPomFile,
//								(IFile) memberPom, true, monitor);
//					}
//				}
//			}
		} catch (OperationCanceledException ex) {
			throw ex;

		} catch (InvalidArtifactRTException ex) {
			// TODO: add message to console 
		} catch (Throwable ex) {
			String msg = "Unable to read model from " + pomFile.getFullPath();
			Maven2Plugin.log(msg, ex);
		} finally {
			monitor.done();
		}
	}

	private static MavenProject getMavenProject(IFile pomFile,
			MavenExecutionResult result) {
//		deleteMarkers(pomFile);

		if (!result.hasExceptions()) {
			return result.getMavenProject();
		}

		for (Iterator it = result.getExceptions().iterator(); it.hasNext();) {
			Exception ex = (Exception) it.next();
			if (ex instanceof ProjectBuildingException) {
				handleProjectBuildingException(pomFile,
						(ProjectBuildingException) ex);

			} else if (ex instanceof AbstractArtifactResolutionException) {
				String msg = ex.getMessage().replaceAll("----------", "")
						.replaceAll("\r\n\r\n", "\n").replaceAll("\n\n", "\n");
//				addMarker(pomFile, msg, 1, IMarker.SEVERITY_ERROR);

				try {
					// TODO
					File file = pomFile.getLocation().toFile();
					return embedderManager.getProjectEmbedder().readProject(
							file);

				} catch (ProjectBuildingException ex2) {
					handleProjectBuildingException(pomFile, ex2);
				} catch (Exception ex2) {
					handleBuildException(pomFile, ex2);
				}

			} else {
				handleBuildException(pomFile, ex);
			}
		}
		return null;
	}

	private static Path materializeArtifactPath(MavenEmbedder embedder,
			MavenProject mavenProject, Artifact a, String type, String suffix,
			boolean download, IProgressMonitor monitor) throws Exception {
		String artifactLocation = a.getFile().getAbsolutePath();

		File file;
		if ("java-source".equals(type)
				&& "tests".equals(a.getArtifactHandler().getClassifier())) {
			suffix = "test-sources";
			file = new File(artifactLocation.substring(0, artifactLocation
					.length()
					- "-tests.jar".length())
					+ "-" + suffix + ".jar");
		} else {
			// artifactLocation ends on '.jar' or '.zip'
			file = new File(artifactLocation.substring(0, artifactLocation
					.length()
					- ".jar".length())
					+ "-" + suffix + ".jar");
		}

		if (file.exists()) {
			// workaround to not download already existing archive
			return new Path(file.getAbsolutePath());
		}

		if (download) {
			monitor.beginTask("Resolve " + type + " " + a.getId(),
					IProgressMonitor.UNKNOWN);
			try {
				// TODO need to take into account issue with the "test-sources"
				Artifact currentArtefact = embedder.createArtifactWithClassifier(a
						.getGroupId(), a.getArtifactId(), a.getVersion(), type,
						suffix);
				if (currentArtefact != null) {
					embedder.resolve(currentArtefact, mavenProject
							.getRemoteArtifactRepositories(), embedder
							.getLocalRepository());
					return new Path(currentArtefact.getFile().getAbsolutePath());
				}
			} catch (AbstractArtifactResolutionException ex) {
				String name = ex.getGroupId() + ":" + ex.getArtifactId() + "-"
						+ ex.getVersion() + "." + ex.getType();
			} finally {
				monitor.done();
			}
		}
		return null;
	}
	
	private static void handleBuildException(IFile pomFile, Exception ex) {
		String msg = Messages.getString("plugin.markerBuildError")
				+ ex.getMessage();
	}

	private static void handleProjectBuildingException(IFile pomFile,
			ProjectBuildingException ex) {
		Throwable cause = ex.getCause();
		if (cause instanceof XmlPullParserException) {
			XmlPullParserException pex = (XmlPullParserException) cause;
			String msg = Messages.getString("plugin.markerParsingError")
					+ pex.getMessage();
		} else if (ex instanceof InvalidProjectModelException) {
			InvalidProjectModelException mex = (InvalidProjectModelException) ex;
			ModelValidationResult validationResult = mex.getValidationResult();
			String msg = Messages.getString("plugin.markerBuildError")
					+ mex.getMessage();
			// TODO : add message to console
		} else {
			handleBuildException(pomFile, ex);
		}
	}

	private static final List VERSIONS = Arrays
			.asList("1.1,1.2,1.3,1.4,1.5,1.6,1.7".split(","));
}
